﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using Chisel.Core.Connections;

namespace Chisel.Core
{
	/// <summary>
	/// Drawing styles for a connection.
	/// </summary>
	public enum ConnectionStyle { Diagonal, Square, Round };

	/// <summary>
	/// A line that connects two rectangles.
	/// </summary>
	public class Connection : Actor
	{

		public Connection()
			: base()
		{
			_canvas = new Canvas() {
			};
			Content = _canvas;
		}

		protected override void OnLoaded(object sender, RoutedEventArgs e)
		{
			base.OnLoaded(sender, e);

			// create the actor anchors
			StartEndPoint = new EndPoint(this);
			_canvas.Children.Add(StartEndPoint);
			StopEndPoint = new EndPoint(this);
			_canvas.Children.Add(StopEndPoint);

			ComputeSegments();
		}

		/// <summary>
		/// The connection uses a canvas to arrange the segments.
		/// </summary>
		private Canvas _canvas;

		/// <summary>
		/// Converts a point in the global Pointinate system to one in the local canvas system.
		/// </summary>
		public Point GlobalToLocal(Point point)
		{
			return new Point(point.X - Canvas.GetLeft(this), point.Y - Canvas.GetTop(this));
		}

		private Actor _start;
		/// <summary>
		/// The actor at the beginning of the connection.
		/// </summary>
		public Actor Start
		{
			get { return _start; }
			set
			{
				if (_start != null)
				{
					_start.Moved -= OnEndMoved;
					_start.Resized -= OnEndResized;
				}
				_start = value;
				_start.Moved += OnEndMoved;
				_start.Resized += OnEndResized;
				if (IsLoaded)
					ComputeSegments();
			}
		}

		/// <summary>
		/// The end point at the start of the connection.
		/// </summary>
		public EndPoint StartEndPoint { get; private set; }

		private Actor _stop;
		/// <summary>
		/// The actor at the end of the connection.
		/// </summary>
		public Actor Stop
		{
			get { return _stop; }
			set
			{
				if (_stop != null)
				{
					_stop.Moved -= OnEndMoved;
					_stop.Resized -= OnEndResized;
				}
				_stop = value;
				_stop.Moved += OnEndMoved;
				_stop.Resized += OnEndResized;
				if (IsLoaded)
					ComputeSegments();
			}
		}

		/// <summary>
		/// The end point at the stop of the connection.
		/// </summary>
		public EndPoint StopEndPoint { get; private set; }


		private Decorator _startDecorator;
		/// <summary>
		/// The decorator to be used at the beginning of this connection.
		/// </summary>
		public Decorator StartDecorator
		{
			get { return _startDecorator; }
			set
			{
				_startDecorator = value;
				if (StartEndPoint != null)
					StartEndPoint.Decorator = _startDecorator;
			}
		}

		private Decorator _stopDecorator;
		/// <summary>
		/// The decorator to be used at the end of this connection.
		/// </summary>
		public Decorator StopDecorator
		{
			get { return _stopDecorator; }
			set
			{
				_stopDecorator = value;
				if (StartEndPoint != null)
					StartEndPoint.Decorator = _stopDecorator;
			}
		}

		/// <summary>
		/// Handles one of the ends moving.
		/// </summary>
		protected virtual void OnEndMoved(Actor end)
		{
			if (IsLoaded)
				ComputeSegments();
			RaiseMoved();
		}

		/// <summary>
		/// Handles one of the ends resizing.
		/// </summary>
		protected virtual void OnEndResized(Actor end)
		{
			if (IsLoaded)
				ComputeSegments();
			RaiseMoved();
		}

        /// <summary>
        /// Resets the style based on the current hit state and the canvas style provider.
        /// </summary>
        protected override void ResetStyle()
        {
			foreach (var segment in _segments)
				segment.ResetStyle();
        }

		public override Point GetConnectionPoint(Point point, out Point? dir)
		{
			throw new NotImplementedException();
		}


		#region Mouse Interaction

		protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
		{
			base.OnMouseLeftButtonDown(e);

			var hit = ParentCanvas.GetTopHit(e);
			if (hit != null)
			{
				if (Start == null)
					Start = hit;
				else if (Stop == null)
					Stop = hit;
				ParentCanvas.Actors.EndDrag(e);
			}
		}

		protected override void OnMouseEnter(MouseEventArgs e)
		{
			base.OnMouseEnter(e);

			AnchorVisibility = Visibility.Visible;
		}

		protected override void OnMouseLeave(MouseEventArgs e)
		{
			base.OnMouseLeave(e);

			AnchorVisibility = Visibility.Collapsed;
		}

		/// <summary>
		/// Selects only this connection and begins dragging whichever end is loose.
		/// </summary>
		public void CaptureCursor()
		{
			ParentCanvas.Actors.DeselectAll();
			ParentCanvas.Actors.Select(this);
			ParentCanvas.Actors.BeginDrag(null);
		}

		#endregion


		#region Dragging

		/// <summary>
		/// This point is used as the Stop if Stop is null.
		/// </summary>
		private Point _dragPoint = new Point();

		public override void OnBeginDrag(MouseButtonEventArgs e)
		{
			base.OnBeginDrag(e);
		}

		public override void OnDrag(MouseEventArgs e)
		{
			base.OnDrag(e);
			_dragPoint = e.GetPosition(ParentCanvas);
			ComputeSegments();
		}

		public override void OnEndDrag(MouseButtonEventArgs e)
		{
			base.OnEndDrag(e);
			
			ComputeSegments();
		}

		#endregion


		#region Segments

		/// <summary>
		/// The connection anchors force the connection to go through a specific point.
		/// </summary>
		private List<Anchor> _anchors = new List<Anchor>();

		/// <summary>
		/// Sets the visibility of all anchors.
		/// </summary>
		private Visibility AnchorVisibility
		{
			set
			{
				//StartEndPoint.Visibility = value;
				foreach (var anchor in _anchors)
					anchor.Visibility = value;
				//StopEndPoint.Visibility = value;
			}
		}

		/// <summary>
		/// The segments connect the anchors and start/stop actors together.
		/// </summary>
		private List<Segment> _segments = new List<Segment>();

		/// <summary>
		/// Forces the connection to recompute the layout of its segments.
		/// </summary>
		public void ComputeSegments()
		{
			if (Start == null && Stop == null) // nothing really to do here
				return;

			// determine the locations of the actor anchors
			Point? dir1 = null, dir2 = null;
			if (Start != null)
			{
				Point stopPoint = _dragPoint;
				if (Stop != null)
					stopPoint = Stop.Center;
				StartEndPoint.Position = Start.GetConnectionPoint(stopPoint, out dir1);
				StartEndPoint.Direction = dir1.Value;
			}
			else
				StopEndPoint.Position = _dragPoint;
			if (Stop != null)
			{
				Point startPoint = _dragPoint;
				if (Start != null)
					startPoint = Start.Center;
				StopEndPoint.Position = Stop.GetConnectionPoint(startPoint, out dir2);
				StopEndPoint.Direction = dir2.Value;
			}
			else
				StopEndPoint.Position = _dragPoint;

			// try to handle making orthagonal lines where they're possible but not generated automatically
			if (dir1 != null && dir2 != null && dir1 == dir2.Value.Negate() && 
				Start != null && Stop != null) // the ends are facing each other and there are actors on both sides
			{
				double edge = ParentCanvas.StyleProvider.EntityRadius * 2;
				if (dir1.Value.Orientation() == Orientation.Vertical)
				{
					double mid = (StartEndPoint.Position.X + StopEndPoint.Position.X) / 2;
					if (Math.Abs(mid - Start.Center.X) <= Start.Width / 2 - edge &&
						Math.Abs(mid - Stop.Center.X) <= Stop.Width / 2 - edge)
					{
						StartEndPoint.Position = new Point(mid, StartEndPoint.Position.Y);
						StopEndPoint.Position = new Point(mid, StopEndPoint.Position.Y);
					}
				}
				else // horizontal
				{
					double mid = (StartEndPoint.Position.Y + StopEndPoint.Position.Y) / 2;
					if (Math.Abs(mid - Start.Center.Y) <= Start.Height / 2 - edge &&
						Math.Abs(mid - Stop.Center.Y) <= Stop.Height / 2 - edge)
					{
						StartEndPoint.Position = new Point(StartEndPoint.Position.X, mid);
						StopEndPoint.Position = new Point(StopEndPoint.Position.X, mid);
					}
				}
			}

			// position the canvas
			Canvas.SetLeft(this, Math.Min(StartEndPoint.Position.X, StopEndPoint.Position.X));
			Canvas.SetTop(this, Math.Min(StartEndPoint.Position.Y, StopEndPoint.Position.Y));
			_canvas.Width = Math.Abs(StartEndPoint.Position.X - StopEndPoint.Position.X);
			_canvas.Height = Math.Abs(StartEndPoint.Position.Y - StopEndPoint.Position.Y);

			StartEndPoint.Relayout();
			StopEndPoint.Relayout();

			// layout the anchors
			foreach (var anchor in _anchors)
				anchor.Relayout();

			// redo the segments
			if (_segments.Count != _anchors.Count + 1) // need a different number of segments
			{
				foreach (var segment in _segments)
					_canvas.Children.Remove(segment);
				_segments.Clear();
				for (int i = 0; i <= _anchors.Count; i++)
				{
					var segment = new Segment(this);
					_segments.Add(segment);
					_canvas.Children.Add(segment);
				}
			}
			for (int i = 0; i <= _anchors.Count; i++)
			{
				if (i == 0) // first segment
				{
					_segments[i].Start = StartEndPoint;
					if (_anchors.Count > 0)
						_segments[i].Stop = _anchors[0];
					else // no anchors
					{
						_segments[i].Stop = StopEndPoint;
					}
				}
				else if (i == _segments.Count - 1) // last s segmenegment
				{
					_segments[i].Stop = StopEndPoint;
					if (_anchors.Count > 0)
						_segments[i].Start = _anchors[-1];
					else
						_segments[i].Start = StartEndPoint;
				}
				else // intermediate segment
				{
					_segments[i].Start = _anchors[i - 1];
					_segments[i].Stop = _anchors[i];
				}
				_segments[i].Relayout();
			}


			if (StartDecorator != null)
				StartEndPoint.Decorator = StartDecorator;
			if (StopDecorator != null)
				StopEndPoint.Decorator = StopDecorator;

		}

		#endregion


	}


}
